package com.youlai.boot.mongo;

import com.google.common.collect.Lists;
import com.mongodb.client.MongoCollection;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

@Slf4j
@Service
public class MongoCommonService {

    @Autowired
    private MongoTemplate mongoTemplate;

    public void createCollection(String collectionName) {
        mongoTemplate.createCollection(collectionName);
    }

    public <T> List<?> getList(T entity, String collectionName) {
        Query query = new Query();
        return getList(entity,query,collectionName);
    }

    public <T> List<?> getList(T entity,Query query, String collectionName) {
        MongoCollection<Document> collection = mongoTemplate.getCollection(collectionName);
        long countDocuments = collection.countDocuments();
        if (countDocuments == 0) {
            log.info("查询的集合{}为空", collectionName);
            return null;
        }
        List<?> list = mongoTemplate.find(query, entity.getClass(), collectionName);
        log.info("查询的集合-{}-数据条数为:{}", collectionName, list.size());
        return list;
    }

    public <T> T getOne(T entity, Query query) {
        Object object = mongoTemplate.findOne(query, entity.getClass());
        return (T) object;
    }

    /**
     * 插入重复数据时：insert报DuplicateKeyException提示主键重复；save对已存在的数据进行更新。
     * 批处理操作时：insert可以一次性插入整个数据，效率较高；save需遍历整个数据，一次插入或更新，效率较低。
     * */
    public <T> T saveOne(T entity) {
        return mongoTemplate.save(entity);
    }

    public <T> void insertOne(T entity) {
        mongoTemplate.insert(entity);
    }

    public <T> void insertOne(T entity, String collectionName) {
        mongoTemplate.insert(entity, collectionName);
    }

    public <T> void insertList(List<T> list) {
        list.forEach(this::insertOne);
        log.info("插入的集合数量{}",list.size());
    }

    public <T> void insertList(List<T> list, String collectionName) {
//        list.forEach(insertOne(); collectionName);
        log.info("插入的集合数量{}",list.size());
        list.forEach(entity -> insertOne(entity,collectionName));

    }

    /**
     * updateFirst更新一个，update更新所有符合条件的
     * */
    public <T> void updateOne(T entity,Update update,Query query) {
        mongoTemplate.updateFirst(query, update, entity.getClass());
    }

    public <T> void update(T entity) {
        mongoTemplate.update(entity.getClass());
    }

    /**
     * Criteria 使用样例
     */
    private void criteriaUsageSample() {
        // 精确查询 { "username" : "admin" } 初始化 criteria 实例的两种方法
        Criteria criteria = Criteria.where("username").is("admin");
        Criteria cri = new Criteria("username").is("admin");

        // 不等于查询 { "username" : { $ne : "admin" } }
        Criteria ne = Criteria.where("username").ne("admin");

        // 模糊查询 { "username" : /admin/ }
        Criteria regex = Criteria.where("username").regex("^.*" + "admin" + ".*$");

        // and 查询 { "username" : "admin", "phoneNumber" : "10086" }
        Criteria and = criteria.and("phoneNumber").is("10086");
        and = new Criteria().andOperator(Criteria.where("username").is("admin"),
                Criteria.where("phoneNumber").is("10086"));

        // or 查询 $or:[ { "username" : "admin" }, { "username" : "anonymous" } ]
        Criteria or = criteria.orOperator(Criteria.where("username").is("admin"),
                Criteria.where("username").is("anonymous"));

        // in 查询 { "username" : { $in: ["admin", "anonymous"] } }
        Criteria in = Criteria.where("username").in(Lists.newArrayList("admin", "anonymous"));

        // nin 查询 { "username" : { $nin: ["admin", "anonymous"] } }
        Criteria nin = Criteria.where("username").nin(Lists.newArrayList("admin", "anonymous"));

        // lt/lte 比较查询
        // 小于等于 { "crtDateTime": { $lte: ISODate("2001-01-01T00:00:00.000+08:00") } }
        Criteria lte = Criteria.where("crtDateTime").lte(LocalDateTime.now());
        // { "age": { $lte: 18 } }
        lte = Criteria.where("age").lte(18);

        // 小于 { "crtDateTime": {$lt: ISODate("2001-01-01T00:00:00.000+08:00") } }
        Criteria lt = Criteria.where("crtDateTime").lt(LocalDateTime.now());
        // { "age": { $lt: 18 } }
        lt = Criteria.where("age").lt(18);

        // gt/gte 比较查询
        // 大于等于 { "crtDateTime": {$gte: ISODate("2001-01-01T00:00:00.000+08:00") } }
        Criteria gte = Criteria.where("crtDateTime").gte(LocalDateTime.now());
        // { "age": { $gte: 18 } }
        gte = Criteria.where("age").gte(18);

        // 大于 { "crtDateTime": {$gt: ISODate("2001-01-01T00:00:00.000+08:00") } }
        Criteria gt = Criteria.where("crtDateTime").gt(LocalDateTime.now());
        // { "age": { $gt: 18 } }
        gt = Criteria.where("age").gt(18);

        // 查询内嵌文档 { "usernameList" : { $elemMatch: { "username" : "admin" } } }
        Criteria elemMatch = Criteria.where("usernameList").elemMatch(Criteria.where("username").is("admin"));

        // api 中无具体接口的， 使用 document 拼接语句查询 { $expr : { $ne : [ "$A", "$B" ] } }
        Criteria andDocumentStructureMatches = criteria.andDocumentStructureMatches(() ->
                new Document().append("$expr", new Document("$ne", List.of("$A", "$B"))));
    }

    /**
     * 查询操作
     *
     * 根据查询条件查询
     * public <T> List<T> find(Query query, Class<T> entityClass) {}
     * 根据查询条件查询返回一条记录
     * public <T> <T> findOne(Query query, Class<T> entityClass) {}
     * 查询该collection所有记录
     * public <T> List<T> findAll(Class<T> entityClass) {}
     */
    private void query() {
        // 组装查询条件（参数 Criteria 的详细用法见 criteriaUsageSample()）
        Query query = new Query(Criteria.where("username").is("admin"));
        // 查询唯一一条满足条件的数据（如果满足条件的数据多于1条，会报错）
//        UserInfo one = mongoTemplate.findOne(query, UserInfo.class);
        // 查询满足条件的数据列表
//        List<UserInfo> list = mongoTemplate.find(query, UserInfo.class);
        // 查询所有记录
//        List<UserInfo> all = mongoTemplate.findAll(UserInfo.class);
        // 根据 filed 去重查询
//        List<UserInfo> distinctList = mongoTemplate.findDistinct(query, "username", UserInfo.class, UserInfo.class);
        // 查询总数
//        long count = mongoTemplate.count(query, UserInfo.class);
    }

    /**
     * 插入操作
     *
     * 新增一条记录
     * public <T> T insert(T objectToSave) {}
     * 在collectionName中新增一条记录
     * public <T> T insert(T objectToSave, String collectionName) {}
     * 保存一条记录
     * public <T> T save(T objectToSave) {}
     */
    private void insert() {
//        mongoTemplate.insert(new UserInfo());
//        mongoTemplate.insert(new UserInfo(), "userInfo");
//        mongoTemplate.save(new UserInfo());
    }

    /**
     * 删除操作
     * <p>
     * 根据Object删除
     * public DeleteResult remove(Object object) {}
     * 根据查询条件进行删除
     * public DeleteResult remove(Query query, Class<?> entityClass) {}
     */
    private void remove() {
//        mongoTemplate.remove(new UserInfo());
//        DeleteResult deleteResult = mongoTemplate.remove(new UserInfo(), "userInfo");
        // 是否执行成功
//        deleteResult.wasAcknowledged();
        // 删除数量
//        deleteResult.getDeletedCount();
    }

    /**
     * 更新操作
     */
    private void update() {
        // 原子操作
        Update update = new Update();
        update.inc("number", 1);
        // 批量更新
//        mongoTemplate.updateMulti(new Query(), update, UserInfo.class);
        // 更新第一条
//        mongoTemplate.updateFirst(new Query(), update, UserInfo.class);
        // 更新数据， 如果不存在就插入
//        UpdateResult updateResult = mongoTemplate.upsert(new Query(), update, UserInfo.class);
        // 是否执行成功
//        updateResult.wasAcknowledged();
        // 匹配到的数量
//        updateResult.getMatchedCount();
        // 更新数量
//        updateResult.getModifiedCount();
        // 插入新数据的id
//        BsonValue upsertedId = updateResult.getUpsertedId();
    }

    /**
     * 聚合操作
     */
    private void aggregate() {
        // 构造聚合操作列表
        List<AggregationOperation> operations = Lists.newArrayList(
                // 匹配操作（参数 Criteria 的详细用法见 criteriaUsageSample()）
                Aggregation.match(new Criteria()),
                // 随机操作（随机取 n 条数据）
                Aggregation.sample(10),
                // 分组操作（GroupOperation 的详细用法见 groupOperationUsageSample()）
                Aggregation.group(),
                // 关联操作
                Aggregation.lookup("targetCollectionName", "_id", "_id", "res"),
                // 拆分操作
                Aggregation.unwind("res"),
                // 排序操作
                Aggregation.sort(Sort.by("")),
                // 跳过操作
                Aggregation.skip(100),
                // 限制操作 （skip + limit 组合可以应用在复杂聚合的分页查询上）
                Aggregation.limit(10),
                // 投射操作（projectionOperation 的详细用法见 projectionOperationUsageSample()）
                Aggregation.project()
        );
        // 构造聚合函数
        Aggregation aggregation = Aggregation.newAggregation(operations);
        // 聚合查询
//        AggregationResults<UserInfo> aggregate = mongoTemplate.aggregate(aggregation, UserInfo.class, UserInfo.class);
        // 获得聚合查询结果集
//        List<UserInfo> mappedResults = aggregate.getMappedResults();
        // 获得聚合查询结果
//        UserInfo uniqueMappedResult = aggregate.getUniqueMappedResult();
        // 如无具体类接收返回结果 可用 org.bson.Document 接收
//        AggregationResults<Document> docs = mongoTemplate.aggregate(aggregation, UserInfo.class, Document.class);
//        List<Document> results = docs.getMappedResults();
//        for (Document result : results) {
//            var param = Optional.ofNullable(result.get("username")).orElse(null);
//        }
    }
}
